pub mod cons_error;
pub mod error_trait;
use actix_http::body::BoxBody;
use actix_web::http::{header, StatusCode};
use actix_web::{Error as WebError, HttpResponse, ResponseError};
use serde::Serialize;
use serde_json::{json, Error as JSONError};
use sled::Error as SledError;
use std::fmt::Display;
use std::io::Error as IOError;
use wither::mongodb::error::Error as MongodbError;
use wither::{bson::oid::Error as OidError, bson::ser::Error as BsonSerError, WitherError};
use valico::json_schema::SchemaError;
use self::error_trait::{CodeError, ConstContentError};

#[derive(thiserror::Error, Debug)]
pub enum AppError {
    #[error("bis error code:{0} message:{1}")]
    Bis(i64, String),
    #[error("io error {0}")]
    IO(#[from] IOError),
    #[error("json error {0}")]
    JSON(#[from] JSONError),
    #[error("mdb error {0}")]
    MDB(#[from] WitherError),
    #[error("mongodb error {0}")]
    Mongodb(#[from] MongodbError),
    #[error("sled error {0}")]
    Sled(#[from] SledError),
    #[error("status error code:{0} message:{1}")]
    Status(StatusCode, String),
    #[error("web error {0}")]
    Web(#[from] WebError),
    #[error("oid error {0}")]
    Oid(#[from] OidError),
    #[error("bson ser error {0}")]
    BsonSer(#[from] BsonSerError),
    #[error("cons error {0}")]
    ConsError(ConstContentError),
    #[error("schema error {0}")]
    SchemaError(#[from] SchemaError),
    #[error("other error {0}")]
    Other(String),
}
impl AppError {
    #[inline]
    pub fn other_error<T: Display>(e: T) -> AppError {
        AppError::Other(e.to_string())
    }
    #[inline]
    pub fn no_permission<T: Into<ConstContentError>>(permission_type: T) -> AppError {
        AppError::ConsError(permission_type.into())
    }
    #[inline]
    pub fn in_black_list() -> AppError {
        AppError::ConsError(cons_error::MSG_IN_BLACK_LIST)
    }
    #[inline]
    pub fn not_found<T: Into<ConstContentError>>(not_found_type: T) -> AppError {
        AppError::ConsError(not_found_type.into())
    }
    #[inline]
    pub fn internal_error<T: Into<ConstContentError>>(internal_error_type: T) -> AppError {
        AppError::ConsError(internal_error_type.into())
    }

    #[inline]
    pub fn forbidden<T: Into<ConstContentError>>(forbidden_type: T) -> AppError {
        AppError::ConsError(forbidden_type.into())
    }
}
pub type AppResult<T> = Result<T, AppError>;

pub trait ToResponse {
    fn to_response(&self) -> HttpResponse;
}

impl<T: Serialize> ToResponse for AppResult<T> {
    fn to_response(&self) -> HttpResponse {
        match self {
            Ok(ok) => HttpResponse::Ok().json(ok),
            Err(e) => e.error_response(),
        }
    }
}

// Error 已经实现了Display

impl ResponseError for AppError {
    fn status_code(&self) -> StatusCode {
        match self {
            AppError::Bis(_, _) => StatusCode::INTERNAL_SERVER_ERROR,
            AppError::IO(_) => StatusCode::INTERNAL_SERVER_ERROR,
            AppError::MDB(_) => StatusCode::INTERNAL_SERVER_ERROR,

            AppError::Status(code, _) => *code,
            AppError::ConsError(c) => c.get_status_code(),
            _ => StatusCode::INTERNAL_SERVER_ERROR,
        }
    }
    fn error_response(&self) -> HttpResponse {
        let code = self.status_code();
        let mut res = HttpResponse::new(code);

        let buf = format!("{}", self);

        log::debug!("Request Has Error: {}", buf);

        res.headers_mut().insert(
            header::CONTENT_TYPE,
            header::HeaderValue::from_static("application/json"),
        );
        match self {
            AppError::ConsError(e) => res.set_body(BoxBody::new(
                json!({
                    "err":buf,
                    "code":e.get_code()
                })
                .to_string(),
            )),
            _ => res.set_body(BoxBody::new(
                json!({
                    "err":buf,
                    "code":code.as_u16()
                })
                .to_string(),
            )),
        }
    }
}
